﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;

namespace IRCClient
{
    /// <summary>
    /// Klasi sem sér um að tengjast servernum og lesa og skrifa frá honum.
    /// </summary>
	class IRCClient
	{
		IPEndPoint server;
		Thread _readThread;
		Thread _writeThread;
		StreamReader reader;
		StreamWriter writer;
		TcpClient tcpClient;
		IRCLogger logger;
		DCC dccConnection;
		bool isConnected;
		string currentNick;
		string userName;
		string realName;
		string currentChannel;
		LinkedList<String> currentTargets;
		List<String> nicks;
		Boolean showCurrent = false;
		DateTime now;

        //Smiður sem upphafsstillir server-inn útfrá endapunkti serversins (port og ip-addressu)
        //Einnig kallar í connect fallið sem sér um að tengjast við hann.
		public IRCClient(IPEndPoint server)
		{
			this.server = server;
			Console.Write("Nickname: ");
			currentNick = Console.ReadLine();
			Console.Write("User: ");
			userName = Console.ReadLine();
			Console.Write("Real name: ");
			realName = Console.ReadLine();

			Connect();
		}

        /// <summary>
        /// Setur upp tengingu, logger og startar les- og skrifþráðum.
        /// </summary>
		public void Connect()
		{
			tcpClient = new TcpClient();
			tcpClient.ReceiveTimeout = 600 * 1000;
			tcpClient.SendTimeout = 600 * 1000;
			tcpClient.Connect(server.Address.ToString(), server.Port);

			isConnected = true;
			logger = new IRCLogger("irc.log");

			reader = new StreamReader(tcpClient.GetStream());
			writer = new StreamWriter(tcpClient.GetStream());

			currentTargets = new LinkedList<String>();
			nicks = new List<String>();
			nicks.Add(currentNick);

			writer.NewLine = "\r\n"; // CR/LF par
			writer.AutoFlush = true; // Flush milli skilaboða.

			_readThread = new Thread(new ThreadStart(ReadThread));
			_writeThread = new Thread(new ThreadStart(WriteThread));
			_readThread.Start();
			_writeThread.Start();
		}

        /// <summary>
        /// Private fall sem sér um lesþráð.
        /// </summary>
		private void ReadThread()
		{
			// Les skilaboð frá server
			while (isConnected)
			{
				now = DateTime.Now;
				string line = reader.ReadLine();
				
				// Parsar skilaboð og prentar í console á réttan hátt
				if (String.IsNullOrWhiteSpace(line) == false)
				{
					Message message = new Message().Parse(line);

					if (message.type == MessageType.ServerMessage)
					{
						Console.WriteLine("* " + message.message);

						// Hack til að sýna núverandi glugga eftir join
						if (message.message == "End of /NAMES list.")
						{
							showCurrent = true;
						}
					}

					else if (message.type == MessageType.PrivateMessage)
					{
						int exclamationPos = message.source.IndexOf("!");

						// Tjékkar hvort það sé private message eða channel message
						if (message.target == currentNick)
							Console.WriteLine("[{0}] Query <{1}> {2}", now.ToLongTimeString(), message.sourceNick, message.message);
						else
							Console.WriteLine("[{0}] {1} <{2}> {3}", now.ToLongTimeString(), message.target, message.sourceNick, message.message);
					}
					else if (message.type == MessageType.CtcpMessage)
					{
						// Styður bara version request
						int exclamationPos = message.source.IndexOf("!");
						if (message.message == Constants.CtcpChar + "VERSION" + Constants.CtcpChar)
						{
							Console.WriteLine("[{0}] CTCP VERSION request from {1}", now.ToLongTimeString(), message.sourceNick);
							writer.WriteLine("NOTICE {0} :{1}{2}{3}", message.sourceNick, Constants.CtcpChar, Constants.CtcpVersionResponse, Constants.CtcpChar);
						}
					}
					else if (message.type == MessageType.Join)
					{
						Console.WriteLine("[{0}] * {1} joined {2}", now.ToLongTimeString(), message.sourceNick, message.target);
					}
					else if (message.type == MessageType.Part)
					{
						Console.WriteLine("[{0}] * {1} parts {2}", now.ToLongTimeString(), message.sourceNick, message.target);
					}
					else if (message.type == MessageType.Quit)
					{
						Console.WriteLine("[{0}] * {1} quits", now.ToLongTimeString(), message.sourceNick);
					}
					else if (message.type == MessageType.DCCRequest)
					{
						string[] lineArr = message.message.Split(' ');

						//fileName-ið er þriðja stakið í array-inu.
						string fileName = lineArr[2];

						//Host-inn er fjórða stakið replace-að svigum með bili og tekur út öll bil í byrjun og enda strengsins.
						string host = lineArr[3].Replace('(', ' ').Replace(')', ' ').Trim();
						int fileSize = int.Parse(lineArr[5].Replace("\x001", ""));

						Console.WriteLine("[{0}] DCC Send Request from {1}. File: {2}. Size: {3}", now.ToLongTimeString(), message.source, fileName, fileSize);

						//Býr til nýja tcp tengingu með DCC klasanum
						dccConnection = new DCC(host, fileName, fileSize);

						// Ef það kom villa í sendingu
						if (String.IsNullOrWhiteSpace(dccConnection.error) == false)
						{
							writer.WriteLine("PRIVMSG {0} :{1}", message.sourceNick, dccConnection.error);
						}
					}

					else if (message.type == MessageType.Ping)
					{
						writer.WriteLine("PONG");
					}

					else if (message.type == MessageType.Error)
					{
						// Ef við fáum error á nickið, setja currentNick sem nickið á undan
						if (message.replyCode == ReplyCode.ErrorNicknameInUse)
						{
							if (nicks.Count > 1)
							{
								nicks.Remove(currentNick);
								currentNick = nicks[nicks.Count - 1];
							}
						}
						Console.WriteLine("* Error: {0} \n", message.message);
					}

					// Loggar hráu línuna
					logger.Write("server", line);
				}
				else
					isConnected = false;
			}

			tcpClient.Close();
		}

        /// <summary>
        /// Skrifþráður sem sér um að parse'a skilaboð frá notanda og senda til servers.
        /// </summary>
		private void WriteThread()
		{
			string line;
			
			// Senda nick, username og realname eftir connect
			writer.WriteLine("NICK {0}", currentNick);
			writer.WriteLine("USER {0} 8 * :{1}", userName, realName);

			// Tekur við skipunum frá notanda
			while (true)
			{
				// Skrifar út núverandi glugga
				if (currentTargets.Count > 0)
				{
					//showCurrentWindow();
				}

				line = Console.ReadLine();
				//Console.SetCursorPosition(0, Console.CursorTop - 1);
				//ClearCurrentConsoleLine();
				now = DateTime.Now;

                //Splitta línunni á bilum og geymt í cmd array.
				String[] cmdArr = line.Trim().Split(' ');

				// Parsa skipunina og senda til server
				if (String.IsNullOrEmpty(line) == false)
				{
                    //Logga að línan komi frá client
					logger.Write("client", line);

					if (isConnected)
					{
						if (currentTargets.Count != 0 && cmdArr[0] == currentTargets.Last.Value)
							cmdArr = cmdArr.Skip(1).ToArray();

						string cmd = cmdArr[0].Trim().ToLower();

						if (cmd == "/nick")
						{
							// Geymir nickið í array
							currentNick = cmdArr[1];
							nicks.Add(currentNick);

							writer.WriteLine("NICK {0}", currentNick);
						}

						else if (cmd == "/msg" || cmd == "/query")
						{
							string target = cmdArr[1];
							string message = String.Join(" ", cmdArr.Skip(2).ToList());

							// Færir target aftast á listann
							if (currentTargets.Find(target) != null) 
								currentTargets.Remove(currentTargets.Find(target));
							currentTargets.AddLast(target);

							Console.WriteLine("[{0}] {1} <{2}> {3}", now.ToLongTimeString(), target, currentNick, message);

							writer.WriteLine("PRIVMSG {0} :{1}", target, message);
						}

						else if (cmd == "/quit")
						{
							currentTargets.Clear();
							// Skippar fyrsta orði í línu og býr til quit message
							string quitMessage = String.Join(" ", cmdArr.Skip(1).ToList());

							writer.WriteLine("QUIT {0}", quitMessage);
							isConnected = false;
						}

						else if (cmd == "/join" || cmd == "/j")
						{
							// Setur rásina í gluggalistann
							showCurrent = false;
							string channel = cmdArr[1];
							if (channel[0] != '#')
								channel = "#" + channel;

							currentTargets.AddLast(channel);

							writer.WriteLine("JOIN {0}", channel);
						}

						else if (cmd == "/part" || cmd == "/p")
						{
							// Tekur núverandi rás úr gluggalistanum
							string channel;
							if (cmdArr.Length > 1)
								channel = cmdArr[1];
							else
								channel = currentTargets.Last.Value;
							if (currentTargets.Count != 0) currentTargets.RemoveLast();

							writer.WriteLine("PART {0}", channel);
						}

						else if (cmd == "/ctcp")
						{
                            string target = cmdArr[1];
							string type = cmdArr[2];
							
                            writer.WriteLine("PRIVMSG {0} :{1}{2}{3}", target, Constants.CtcpChar, type, Constants.CtcpChar);
						}

						// Venjuleg skilaboð til núverandi "glugga"
						else if (cmd.StartsWith("/") == false)
						{
							if (currentTargets.Count > 0)
							{
								Console.WriteLine("[{0}] {1} <{2}> {3}", now.ToLongTimeString(), currentTargets.Last.Value, currentNick, line);
								writer.WriteLine("PRIVMSG {0} :{1}", currentTargets.Last.Value, line);
							}
							else
								Console.WriteLine("* Error: No channel/query open");
						}
						else
							writer.WriteLine(line.Substring(1));

					}
				}
			}
		}

		// Sýna current "glugga" fremst í línu (ónotað)
		private void showCurrentWindow()
		{
			if (showCurrent)
			{
				Console.ForegroundColor = ConsoleColor.DarkGray;
				Console.Write("[{0}] ", currentTargets.Last.Value);
				Console.ResetColor();
			}
		}

		// Stroka út síðustu línu í console (ónotað)
		private void ClearCurrentConsoleLine()
		{
			int currentLineCursor = Console.CursorTop;
			Console.SetCursorPosition(0, Console.CursorTop);
			Console.Write(new string(' ', Console.WindowWidth));
			Console.SetCursorPosition(0, currentLineCursor);
		}
	}
}
